여러 개의 Bean 후보

@VERO
Created Date · 2023년 05월 22일 09:05
Last Updated Date · 2023년 10월 15일 13:10

Bean의 후보가 여러 개라면?

의존성 주입을 수행할 때 bean이 여러 개라면 오류가 발생한다. 이를 해결하기 위해 3가지 방법을 사용할 수 있다.

  • @Autowired 의 필드 이름 매칭
  • @Qualifier
  • @Primary

가장 잘 일치하는 하나의 bean을 찾을 수 없는 경우에는 NoUniqueBeanDefinitionException 이 발생한다.

사용할 클래스 정리

public interface ExtraFarePolicy {  
    Fare calculateExtraFare(final Path path);  
}
@Component  
public class DistanceBasedExtraFarePolicy implements ExtraFarePolicy {  
	public DistanceBasedExtraFarePolicy() {  
	    System.out.println(getClass());  
	}
	
	@Override  
	public Fare calculateExtraFare(final Path path) {  
		return null;
	}
}
@Component  
public class DummyPolicy implements ExtraFarePolicy{  
    public DummyPolicy() {  
        System.out.println(getClass());  
    }  
  
    @Override  
    public Fare calculateExtraFare(final Path path) {  
        return null;  
    }  
}
@Component  
public class Dummy {  
    public ExtraFarePolicy policy;  
}

@Autowired 필드 이름 매칭

필드 이름으로 주입하는 방법이다.

먼저 필드 주입이 가능하다.

@Component  
public class Dummy {  
    @Autowired  
    private ExtraFarePolicy dummyPolicy;  
}

위와 같은 코드를 작성하면, ExtraFarePolicy 에 들어가는 구현체는 놀랍게도 DummyPolicy 클래스가 된다.

생성자로 주입을 받으면 매개변수 이름이 주입받는 클래스 이름이 된다. distanceBasedExtraFarePolicy 라는 이름으로 주입 받기 때문에 DistanceBasedExtraFarePolicyExtraFarePolicy 에 대입된다.

@Component  
public class Dummy {  
    private final ExtraFarePolicy dummyPolicy;  
  
    public Dummy(final ExtraFarePolicy distanceBasedExtraFarePolicy) {  
        this.dummyPolicy = distanceBasedExtraFarePolicy;  
        System.out.println(a.getClass());  
    }  
}

아예 구현체의 이름으로 존재하지 않는 다른 이름을 적으면 컴파일 에러가 발생한다. 이 경우에는 Spring이 Bean을 주입할 때 여러 구현체 중에 어떤 구현체를 선택해야 할 지 모르기 때문에 컴파일 에러가 발생하게 된다.

@Component  
public class Dummy {  
    @Autowired  
    private ExtraFarePolicy extraFarePolicy;  
}

그러나 이렇게 주입하는 방식은 추천하지 않는다. Spring이 컴파일러에게 높은 우선순위를 부여하는 구현체 기반의 주입 매커니즘과는 관련이 없는 일반적인 빈 이름 기반의 주입 매커니즘이기 때문에, 규칙을 따르는 것이 더 명확하고 안전하다.

@Qualifier

Spring 컨테이너가 동일한 유형의 bean을 여러 개 찾았을 때, 모호함을 해결하기 위해 추가적으로 판단할 수 있는 정보를 제공한다. 주의할 점은 등록되는 bean의 이름을 바꾸는 것이 아닌, 구분자를 추가하는 형식이라는 점이다.

특징

  • 메서드, 필드, 생성자 매개변수에 사용할 수 있다.
  • @Autowired 와 함께 사용해야 한다.

@Qualifier 를 생성자 매개변수에 달아줄 수 있다. 이런 경우에는 생성자 매개변수 이름이 다른 구현체 이름이더라도 정확하게 해당 bean을 등록하게 된다.

@Component  
public class Dummy {  
    private ExtraFarePolicy dummyPolicy;  
  
    public Dummy(@Qualifier("distanceBasedExtraFarePolicy") final ExtraFarePolicy dummyPolicy) {  
        this.dummyPolicy = dummyPolicy;  
        System.out.println("dummy" + dummyPolicy.getClass());  // dummyclass subway.domain.fare.DistanceBasedExtraFarePolicy

    }  
}

필드 주입을 사용하려면 다음과 같이 사용하면 된다.

@Component  
public class Dummy {  
    @Autowired  
    @Qualifier("distanceBasedExtraFarePolicy")  
    public ExtraFarePolicy dummyPolicy;  
}

존재하지 않는 @Qualifier 를 사용하면 NoSuchBeanDefinitionException 이 발생한다.

@Component  
public class Dummy {  
    @Autowired  
    @Qualifier("strangePolicy")  
    public ExtraFarePolicy dummyPolicy;  
}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'subway.domain.fare.ExtraFarePolicy' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value="strangePolicy")}

@Primary

@Primary  
@Component  
public class DummyPolicy implements ExtraFarePolicy {  
    public DummyPolicy() {  
        System.out.println(getClass());  
    }  
  
    @Override  
    public Fare calculateExtraFare(final Path path) {  
        return null;  
    }  
}

우선순위를 주고 싶은 bean의 클래스 상단에 적어주면 된다.

@Primary 어노테이션이 여러 개 존재하는 경우에는 NoUniqueBeanDefinitionException 이 발생한다.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'subway.domain.fare.ExtraFarePolicy' available: more than one 'primary' bean found among candidates: [distanceBasedExtraFarePolicy, dummyPolicy]

@Resource ?

@Resource 어노테이션은 필드 타입으로 bean을 찾는 @Autowired와 달리 bean의 이름으로 bean을 주입한다.

@Component  
public class Dummy {  
    @Resource  
    public ExtraFarePolicy dummyPolicy;  
}

해당 코드에서는 DummyPolicy 를 주입받게 된다.

우선 순위

@Qualifier@Primary 어노테이션이 모두 존재하는 경우, @Qualifier 가 우선된다.

@Qualifier("dummyPolicy")  
@Component  
public class DummyPolicy implements ExtraFarePolicy{  
    
}
@Primary  
@Component  
public class DistanceBasedExtraFarePolicy implements ExtraFarePolicy {
}
@Component  
public class Dummy {  
    @Autowired  
    @Qualifier("dummyPolicy")  
    public ExtraFarePolicy dummyPolicy;  
}

이런 경우에는 DummyExtraFarePolicy 에는 DummyPolicy 가 주입되게 된다.